package syncx

import (
	"sync"
	"sync/atomic"
	"time"

	cmap "github.com/orcaman/concurrent-map"
)

// IDLocker 提供基于ID的锁功能
// 可以对不同的ID加锁，相同ID会阻塞，不同ID不会互相影响
// 带有自动清理功能，长时间未使用的锁会被清理释放
type IDLocker struct {
	locks           cmap.ConcurrentMap // 存储所有锁的并发Map
	cleanupInterval time.Duration      // 清理检查间隔
	expiration      time.Duration      // 锁闲置过期时间
	stopChan        chan struct{}      // 用于停止清理协程
}

// lockData 表示每个ID对应的锁数据
type lockData struct {
	mutex      *sync.Mutex // 互斥锁
	refCount   int32       // 引用计数，原子操作
	lastAccess int64       // 最后访问时间戳，纳秒，原子操作
}

// NewIDLocker 创建一个新的IDLocker实例，默认30分钟过期
func NewIDLocker() *IDLocker {
	return NewIDLockerWithExpiration(30 * time.Minute)
}

// NewIDLockerWithExpiration 创建一个新的IDLocker实例，指定锁过期时间
func NewIDLockerWithExpiration(expiration time.Duration) *IDLocker {
	locker := &IDLocker{
		locks:           cmap.New(),
		cleanupInterval: expiration / 3, // 清理间隔为过期时间的1/3
		expiration:      expiration,
		stopChan:        make(chan struct{}),
	}

	// 启动清理协程
	go locker.cleanupLoop()

	return locker
}

// Lock 对指定ID加锁，如果该ID已被锁定，则会阻塞直到解锁
func (l *IDLocker) Lock(id string) {
	// 获取或创建锁数据
	data := l.getOrCreateLockData(id)

	// 增加引用计数
	atomic.AddInt32(&data.refCount, 1)

	// 加锁
	data.mutex.Lock()

	// 更新最后访问时间
	atomic.StoreInt64(&data.lastAccess, time.Now().UnixNano())
}

// Unlock 解锁指定ID的锁
func (l *IDLocker) Unlock(id string) {
	// 获取锁数据
	val, ok := l.locks.Get(id)
	if !ok {
		// 锁不存在，可能已被清理
		return
	}

	data := val.(*lockData)

	// 更新最后访问时间
	atomic.StoreInt64(&data.lastAccess, time.Now().UnixNano())

	// 解锁
	data.mutex.Unlock()

	// 减少引用计数
	newCount := atomic.AddInt32(&data.refCount, -1)

	// 如果引用计数降为0，安排延迟删除
	if newCount == 0 {
		// 使用AfterFunc安排延迟删除，但先标记为要删除状态
		// 将引用计数设为一个特殊的负值，表示"计划删除"
		if atomic.CompareAndSwapInt32(&data.refCount, 0, -999999) {
			time.AfterFunc(10*time.Second, func() {
				// 检查是否仍然处于"计划删除"状态
				if atomic.LoadInt32(&data.refCount) == -999999 {
					// 最后再检查一次可用性，然后删除
					if data.mutex.TryLock() {
						l.locks.Remove(id)
						data.mutex.Unlock()
					}
				}
			})
		}
	}
}

// TryLock 尝试对指定ID加锁，如果加锁成功返回true，如果已被锁定返回false
func (l *IDLocker) TryLock(id string) bool {
	// 获取或创建锁数据
	data := l.getOrCreateLockData(id)

	// 尝试加锁
	if !data.mutex.TryLock() {
		return false
	}

	// 加锁成功，增加引用计数
	atomic.AddInt32(&data.refCount, 1)

	// 更新最后访问时间
	atomic.StoreInt64(&data.lastAccess, time.Now().UnixNano())

	return true
}

// LockWithTimeout 尝试在指定时间内对ID加锁，成功返回true，超时返回false
func (l *IDLocker) LockWithTimeout(id string, timeout time.Duration) bool {
	deadline := time.Now().Add(timeout)

	// 使用指数退避策略
	backoff := 1 * time.Millisecond
	maxBackoff := 50 * time.Millisecond

	for time.Now().Before(deadline) {
		if l.TryLock(id) {
			return true
		}

		// 计算下次重试前等待时间
		sleepTime := backoff
		if time.Now().Add(sleepTime).After(deadline) {
			sleepTime = time.Until(deadline)
			if sleepTime <= 0 {
				break
			}
		}

		time.Sleep(sleepTime)

		// 增加退避时间
		backoff *= 2
		if backoff > maxBackoff {
			backoff = maxBackoff
		}
	}

	return false
}

// Close 关闭IDLocker，停止清理协程
func (l *IDLocker) Close() {
	close(l.stopChan)
}

// getOrCreateLockData 获取或创建一个ID的锁数据
func (l *IDLocker) getOrCreateLockData(id string) *lockData {
	// 尝试获取现有锁数据
	val, ok := l.locks.Get(id)
	if ok {
		return val.(*lockData)
	}

	// 创建新的锁数据
	newData := &lockData{
		mutex:      &sync.Mutex{},
		refCount:   0,
		lastAccess: time.Now().UnixNano(),
	}

	// 尝试设置，如果已存在则获取现有的
	success := l.locks.SetIfAbsent(id, newData)
	if success {
		// 成功设置了新数据
		return newData
	} else {
		// 有其他goroutine先设置了，获取他们设置的数据
		val, _ = l.locks.Get(id)
		return val.(*lockData)
	}
}

// cleanupLoop 定期清理未使用的锁
func (l *IDLocker) cleanupLoop() {
	ticker := time.NewTicker(l.cleanupInterval)
	defer ticker.Stop()

	for {
		select {
		case <-ticker.C:
			l.cleanup()
		case <-l.stopChan:
			return
		}
	}
}

// cleanup 清理长时间未使用且无引用的锁
func (l *IDLocker) cleanup() {
	now := time.Now()
	expireNanos := now.Add(-l.expiration).UnixNano()

	// 遍历所有锁
	for tuple := range l.locks.IterBuffered() {
		id := tuple.Key
		data := tuple.Val.(*lockData)

		// 如果引用计数为0且超过过期时间，则删除
		lastAccessTime := atomic.LoadInt64(&data.lastAccess)
		refCount := atomic.LoadInt32(&data.refCount)

		if refCount == 0 && lastAccessTime < expireNanos {
			// 尝试锁定以确保没有其他goroutine正在使用
			if data.mutex.TryLock() {
				// 再次检查引用计数，确保在我们获取锁期间没有被其他goroutine使用
				if atomic.LoadInt32(&data.refCount) == 0 {
					l.locks.Remove(id)
				}
				data.mutex.Unlock()
			}
		}
	}
}
